﻿using Crgd.UAuthServer.T4.Configs;
using Crgd.UAuthServer.T4.Entities;
using DreamX.T4.Entities;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;

namespace Crgd.UAuthServer.T4.Helpers
{
    public class MsSqlDbHelper
    {
        //public static readonly string connectionString = "Data Source=192.168.10.201;User ID=sa;Password=crgd2013;persist security info=False;initial catalog=DeliveryCenter;";
        //public static readonly string connectionString="Data Source=192.168.10.135;User ID=sa;Password=111111;persist security info=False;initial catalog=suibao_jz_db;";
        //public static readonly string database = "DeliveryCenter";

        private readonly DbConfig _dbConfig;

        public MsSqlDbHelper(DbConfig dbConfig)
        {
            _dbConfig = dbConfig;
        }

        #region GetDbTables

        public List<DbTable> GetTablesWithConfig(T4Config config)
        {
            List<DbTable> tables = null;
            if (config.GenTablesWithMoudle != null && config.GenTablesWithMoudle.Count > 0)
            {
                tables = new List<DbTable>();
                foreach (var moudle in config.GenTablesWithMoudle)
                {
                    if (moudle.Value == null || moudle.Value.Count == 0)
                        continue;

                    var genTableNames = string.Join(",", moudle.Value);
                    var tempTables = this.GetDbTables(config.MoudleNameType, genTableNames, moudle.Key);

                    tables.AddRange(tempTables);
                }
            }
            else
            {
                tables = this.GetDbTables(config.MoudleNameType);
            }

            return tables;
        }

        public List<DbTable> GetDbTables(MoudleNameType moudleNameType = MoudleNameType.None, string tables = "", string moudleName = "")
        {
            if (!string.IsNullOrEmpty(tables))
            {
                tables = string.Format(" AND obj.[name] IN ('{0}')", tables.Replace(",", "','"));
            }
            #region SQL
            // TODO: 需要进一步排除系统表，否则可能引起报错
            string queryTablesSql = 
                string.Format(@"SELECT
	                obj.[name] AS Tablename,
	                schem.[name] Schemname,
	                CAST
	                (
		                CASE 
			                WHEN (SELECT COUNT(1) FROM sys.indexes WHERE [object_id]= obj.[object_id] AND is_primary_key=1) >=1 THEN 1
			                ELSE 0
		                END 
	                AS BIT) AS HasPrimaryKey,
	                CAST
	                (
		                (CASE type WHEN 'V' THEN 1 ELSE 0 END)
	                AS BIT) AS IsReadonly,
	                pro.[value] AS Remark
                FROM {0}.sys.objects obj 
                INNER JOIN {0}.sys.schemas schem ON schem.[schema_id]=obj.[schema_id]
                LEFT JOIN {0}.sys.extended_properties pro ON pro.major_id = obj.[object_id] and pro.minor_id = 0
                WHERE (obj.[type]='U' or obj.[type]='V') AND obj.[name] <> '__EFMigrationsHistory' {1}
                ORDER BY obj.[name]", _dbConfig.DatabaseName, tables);
            #endregion

            DataTable dt = GetDataTable(queryTablesSql);
            IList<DbColumn> columns = GetDbColumns(tables);
            foreach (var row in dt.Rows.Cast<DataRow>())
            {
                try
                {
                    new DbTable
                    {
                        MoudleName = moudleNameType == MoudleNameType.TableName ? this.GetMoudleNameByTableName(row.Field<string>("Tablename")) : moudleName,
                        TableName = this.GetFactTableName(moudleNameType, row.Field<string>("Tablename")),
                        SchemaName = row.Field<string>("Schemname"),
                        HasPrimaryKey = row.Field<bool>("HasPrimaryKey"),
                        IsReadonly = row.Field<bool>("IsReadonly"),
                        Remark = row.Field<string>("Remark"),
                        Columns = columns.Where(p => p.TableName == row.Field<string>("Tablename")).ToList()
                    };
                }
                catch (Exception ex)
                {
                    var rowInfo = "";
                    foreach(DataColumn column in dt.Columns)
                    {
                        rowInfo += "\r\n[" + column.ColumnName + "]: " + row[column].ToString();
                    }
                    throw new Exception($"表: {row.Field<string>("Tablename")}获取失败，表内容：{rowInfo}", ex);
                }
            }
            return dt.Rows.Cast<DataRow>().Select(row => new DbTable
            {
                MoudleName = moudleNameType == MoudleNameType.TableName ? this.GetMoudleNameByTableName(row.Field<string>("Tablename")) : moudleName,
                TableName = this.GetFactTableName(moudleNameType, row.Field<string>("Tablename")),
                SchemaName = row.Field<string>("Schemname"),
                HasPrimaryKey = row.Field<bool>("HasPrimaryKey"),
                IsReadonly = row.Field<bool>("IsReadonly"),
                Remark = row.Field<string>("Remark"),
                Columns = columns.Where(p => p.TableName == row.Field<string>("Tablename")).ToList()
            }).ToList();
        }
        #endregion

        #region GetDbColumns

        public List<DbColumn> GetDbColumns(string tables = "", string schema = "dbo")
        {
            if (!string.IsNullOrEmpty(tables) && !tables.StartsWith(" AND obj.[name]"))
            {
                tables = string.Format(" AND obj.[name] IN ('{0}')", tables.Replace(",", "','"));
            }

            #region SQL
            string sql = string.Format(@"
                                    WITH indexCTE AS
                                    (
                                        SELECT 
                                        ic.column_id,
                                        ic.index_column_id,
                                        ic.[object_id]
                                        FROM {0}.sys.indexes AS idx
                                        INNER JOIN {0}.sys.index_columns AS ic ON idx.index_id = ic.index_id AND idx.[object_id] = ic.[object_id]
                                        WHERE idx.is_primary_key=1
                                    )
                                    SELECT
                                    obj.[object_id] AS TableId,
                                    obj.[name] AS TableName,
                                    colm.column_id AS ColumnID,
                                    CAST(CASE WHEN indexCTE.column_id IS NULL THEN 0 ELSE 1 END AS BIT) IsPrimaryKey,
                                    colm.[name] AS ColumnName,
                                    systype.[name] AS ColumnType,
                                    colm.is_identity AS IsIdentity,
                                    colm.is_nullable AS IsNullable,
                                    CAST(colm.max_length as int) AS ByteLength,
                                    (
                                        CASE 
                                            WHEN systype.[name]='nvarchar' and colm.max_length>0 THEN colm.max_length/2 
                                            WHEN systype.[name]='nchar' and colm.max_length>0 THEN colm.max_length/2
                                            WHEN systype.[name]='ntext' and colm.max_length>0 THEN colm.max_length/2 
                                            ELSE colm.max_length
                                        END
                                    ) AS CharLength,
                                    CAST(colm.[precision] as int) AS [Precision],
                                    CAST(colm.scale as int) Scale,
                                    REPLACE(REPLACE(sdc.[definition],'(',''),')','') AS DefaultValue,
                                    prop.[value] AS Remark
                                    FROM {0}.sys.columns AS colm
                                    INNER JOIN {0}.sys.objects AS obj ON obj.[object_id] = colm.[object_id]
                                    INNER JOIN {0}.sys.types AS systype ON colm.system_type_id=systype.system_type_id AND colm.user_type_id=systype.user_type_id
                                    LEFT JOIN {0}.sys.extended_properties AS prop ON colm.[object_id]=prop.major_id AND colm.column_id=prop.minor_id
                                    LEFT JOIN indexCTE ON colm.column_id = indexCTE.column_id AND colm.[object_id]=indexCTE.[object_id]   
                                    LEFT JOIN sys.default_constraints AS sdc ON  obj.[object_id] = sdc.[parent_object_id] AND colm.column_id = sdc.parent_column_id                                    
                                    WHERE (obj.[type] = 'U' OR obj.[type] = 'V') AND obj.[name] <> '__EFMigrationsHistory' {1}
                                    ORDER BY obj.[name], colm.column_id", _dbConfig.DatabaseName, tables);
            #endregion
            // SqlParameter param = new SqlParameter("@tableName", SqlDbType.NVarChar, 100) { Value = string.Format("{0}.{1}.{2}", database, schema, tableName) };
            DataTable dt = GetDataTable(sql);
            return dt.Rows.Cast<DataRow>().Select(row =>
            {
                var column = new DbColumn()
                {
                    ColumnID = row.Field<int>("ColumnID"),
                    TableName = row.Field<string>("TableName"),
                    IsPrimaryKey = row.Field<bool>("IsPrimaryKey"),
                    ColumnName = row.Field<string>("ColumnName"),
                    ColumnType = row.Field<string>("ColumnType"),
                    IsIdentity = row.Field<bool>("IsIdentity"),
                    IsNullable = row.Field<bool>("IsNullable"),
                    ByteLength = row.Field<int>("ByteLength"),
                    CharLength = row.Field<int>("CharLength"),
                    Scale = row.Field<int>("Scale"),
                    DefaultValue = row.Field<string>("DefaultValue"),
                    Remark = row["Remark"].ToString()
                };

                string tempRemark = row.Field<string>("Remark");
                if (!string.IsNullOrEmpty(tempRemark))
                {
                    var remarks = tempRemark.Split(new string[] { System.Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim());
                    if (remarks != null && remarks.Count() > 0)
                    {
                        if (remarks.Count() > 1 || tempRemark.StartsWith(@"@@"))
                        {
                            column.EnumTypeString = remarks.FirstOrDefault(p => p.StartsWith(@"@@"));
                            if (remarks.Count() > 1)
                            {
                                column.CSharpDescription = remarks.First();
                            }
                            else
                            {
                                column.CSharpDescription = column.EnumTypeString.Split(new string[] { @";" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Split(new string[] { @"@@" }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
                            }

                            column.Remark = string.Join($"\" + {Environment.NewLine}\t\t\t\"", remarks);
                        }
                        else
                        {
                            column.CSharpDescription = tempRemark;
                        }
                    }
                }
                return column;
            }).ToList();
        }

        #endregion


        #region GetDataTable

        public DataTable GetDataTable(string commandText, params SqlParameter[] parms)
        {
            DataTable dt = new DataTable();
            using (SqlConnection connection = new SqlConnection(_dbConfig.ConnectionString))
            {
                using (SqlCommand command = connection.CreateCommand())
                {
                    command.CommandText = commandText;
                    if (parms != null)
                    {
                        command.Parameters.AddRange(parms);
                    }
                    SqlDataAdapter adapter = new SqlDataAdapter(command);
                    adapter.Fill(dt);
                }

                connection.Close();
            }
            return dt;
        }

        #endregion

        public string GetMoudleNameByTableName(string tableName)
        {
            if (string.IsNullOrEmpty(tableName))
                return string.Empty;

            return tableName.Split(new string[] { "_" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
        }

        private string GetFactTableName(MoudleNameType moudleNameType, string tableName)
        {
            if (moudleNameType != MoudleNameType.TableName)
                return tableName;

            if (string.IsNullOrEmpty(tableName))
                return string.Empty;

            return tableName.Substring(tableName.IndexOf('_') + 1);
        }
    }

    #region SqlServerDbTypeMap

    public class SqlServerDbTypeMap
    {
        public static string MapCsharpType(string dbtype)
        {
            if (string.IsNullOrEmpty(dbtype)) return dbtype;
            dbtype = dbtype.ToLower();
            string csharpType = "object";
            switch (dbtype)
            {
                case "bigint": csharpType = "long"; break;
                case "binary": csharpType = "byte[]"; break;
                case "bit": csharpType = "bool"; break;
                case "char": csharpType = "string"; break;
                case "date": csharpType = "DateTime"; break;
                case "datetime": csharpType = "DateTime"; break;
                case "datetime2": csharpType = "DateTime"; break;
                case "datetimeoffset": csharpType = "DateTimeOffset"; break;
                case "decimal": csharpType = "decimal"; break;
                case "float": csharpType = "double"; break;
                case "image": csharpType = "byte[]"; break;
                case "int": csharpType = "int"; break;
                case "money": csharpType = "decimal"; break;
                case "nchar": csharpType = "string"; break;
                case "ntext": csharpType = "string"; break;
                case "numeric": csharpType = "decimal"; break;
                case "nvarchar": csharpType = "string"; break;
                case "real": csharpType = "Single"; break;
                case "smalldatetime": csharpType = "DateTime"; break;
                case "smallint": csharpType = "short"; break;
                case "smallmoney": csharpType = "decimal"; break;
                case "sql_variant": csharpType = "object"; break;
                case "sysname": csharpType = "object"; break;
                case "text": csharpType = "string"; break;
                case "time": csharpType = "TimeSpan"; break;
                case "timestamp": csharpType = "byte[]"; break;
                case "tinyint": csharpType = "byte"; break;
                case "uniqueidentifier": csharpType = "Guid"; break;
                case "varbinary": csharpType = "byte[]"; break;
                case "varchar": csharpType = "string"; break;
                case "xml": csharpType = "string"; break;
                default: csharpType = "object"; break;
            }
            return csharpType;
        }

        public static Type MapCommonType(string dbtype)
        {
            if (string.IsNullOrEmpty(dbtype)) return Type.Missing.GetType();
            dbtype = dbtype.ToLower();
            Type commonType = typeof(object);
            switch (dbtype)
            {
                case "bigint": commonType = typeof(long); break;
                case "binary": commonType = typeof(byte[]); break;
                case "bit": commonType = typeof(bool); break;
                case "char": commonType = typeof(string); break;
                case "date": commonType = typeof(DateTime); break;
                case "datetime": commonType = typeof(DateTime); break;
                case "datetime2": commonType = typeof(DateTime); break;
                case "datetimeoffset": commonType = typeof(DateTimeOffset); break;
                case "decimal": commonType = typeof(decimal); break;
                case "float": commonType = typeof(double); break;
                case "image": commonType = typeof(byte[]); break;
                case "int": commonType = typeof(int); break;
                case "money": commonType = typeof(decimal); break;
                case "nchar": commonType = typeof(string); break;
                case "ntext": commonType = typeof(string); break;
                case "numeric": commonType = typeof(decimal); break;
                case "nvarchar": commonType = typeof(string); break;
                case "real": commonType = typeof(Single); break;
                case "smalldatetime": commonType = typeof(DateTime); break;
                case "smallint": commonType = typeof(short); break;
                case "smallmoney": commonType = typeof(decimal); break;
                case "sql_variant": commonType = typeof(object); break;
                case "sysname": commonType = typeof(object); break;
                case "text": commonType = typeof(string); break;
                case "time": commonType = typeof(TimeSpan); break;
                case "timestamp": commonType = typeof(byte[]); break;
                case "tinyint": commonType = typeof(byte); break;
                case "uniqueidentifier": commonType = typeof(Guid); break;
                case "varbinary": commonType = typeof(byte[]); break;
                case "varchar": commonType = typeof(string); break;
                case "xml": commonType = typeof(string); break;
                default: commonType = typeof(object); break;
            }
            return commonType;
        }
    }
    #endregion

}
